﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Fintech.Utils.Database;

namespace ZOVNP.Model.Abstracts
{
    /// <summary> Контекст работы с доменными объектами. </summary>
    public interface IDomainContext : IDisposable
    {

        #region События

        /// <summary> Событие об уничтожении контекста. </summary>
        /// <remarks> От этого события не обязательно отписываться при уничтожении контекста, удаление подписчиков происходит автоматически. </remarks>
        event EventHandler Disposed;

        #endregion

        #region Свойства

        /// <summary> Ссылка на движок, который работает с данным контекстом базы данных и породил его. </summary>
        IDomainEngine Engine { get; }

        /// <summary> Менеджер занесения новых доменных объектов в данный контекст. </summary>
        /// <remarks> Введен для группировки методов добавления. </remarks>
        IContextAdder Adder { get; }

        /// <summary> Менеджер получения данных. </summary>
        /// <remarks> Введен для группировки методов извлечения данных. </remarks>
        IContextGetter Getter { get; }

        #endregion

        #region Методы
        
        /// <summary> Метод либо находит уже существующую в контексте сущность по ключу переданной сущности, либо присоединяет к контексту переданную. </summary>
        /// <param name="Entity"> Сущность, которую необходимо занести в контекст. </param>
        /// <returns> Сущность, занесенная в контекст, либо находящаяся уже там. Во втором случае возвращаемая ссылка на объект отличается от переданной. </returns>
        object FindOrAttach(object Entity);

        /// <summary> Получить типизированный сервис для доступа к объектам. </summary>
        /// <typeparam name="T"> Тип объектов, с которыми работает сервис. </typeparam>
        /// <returns> Сервис, используемый далее для доступа к объектам в источнике данных. </returns>
        IObjectQuery<T> Query<T>() where T : IDomainObject;

        /// <summary> Получение доменного объекта из локального кэша контекста. </summary>
        /// <typeparam name="T"> Тип доменного объекта. </typeparam>
        /// <param name="Entity"> Этот же доменный объект, полученный из неизвестного контекста. </param>
        /// <param name="Option"> Опции запроса, определяющие поведение в случае отсутствия требуемого объекта. </param>
        /// <returns> Объект-близнец заданного, если он есть в кэше. </returns>
        /// <remarks> Локальный кэш контекста формируется в результате выполняемых в рамках этого контекста запросов. </remarks>
        T QueryCache<T>(T Entity, QueryCacheOption Option) where T : class, IDomainObject;

        /// <summary> Получение доменного объекта из локального кэша контекста. </summary>
        /// <typeparam name="T"> Тип доменного объекта. </typeparam>
        /// <param name="ID"> Идентификатор доменного объекта. </param>
        /// <param name="Option"> Опции запроса, определяющие поведение в случае отсутствия требуемого объекта. </param>
        /// <returns> Требуемый доменный объект. </returns>
        /// <remarks> Локальный кэш контекста формируется в результате выполняемых в рамках этого контекста запросов. </remarks>
        T QueryCache<T>(Guid ID, QueryCacheOption Option) where T : class, IDomainObject;

        /// <summary> Получение объектов из локального кэша контекста. </summary>
        /// <typeparam name="T"> Тип доменного объекта. </typeparam>
        /// <param name="Predicate"> Предикат для отбора объектов. </param>
        /// <returns> Коллекция доменных объектов. </returns>
        /// <remarks> Локальный кэш контекста формируется в результате выполняемых в рамках этого контекста запросов. </remarks>
        T[] QueryCache<T>(Func<T, bool> Predicate);

        /// <summary> Получение объектов из локального кэша контекста. </summary>
        /// <typeparam name="T"> Тип доменного объекта. </typeparam>
        /// <returns> Коллекция доменных объектов. </returns>
        /// <remarks> Локальный кэш контекста формируется в результате выполняемых в рамках этого контекста запросов. </remarks>
        T[] QueryCache<T>();

        /// <summary> Получение всех доменных объектов заданного типа. </summary>
        /// <param name="DomainType"> Тип (должен быть типом интерфейса) доменных объектов. </param>
        /// <returns> Массив объектов заданного типа. </returns>
        IDomainObject[] PlainQuery(Type DomainType);

        /// <summary> Получение доменного объекта заданного типа. </summary>
        /// <param name="DomainType"> Тип (должен быть типом интерфейса) доменного объекта. </param>
        /// <param name="EntityID"> Идентификатор доменного объекта </param>
        /// <returns> Экземпляр доменного объекта или null, если такового не найдено. </returns>
        IDomainObject PlainQuery(Type DomainType, Guid EntityID);

        /// <summary> Добавить в хранилище новый доменный объект. </summary>
        /// <param name="Domain"> Объект для добавления к хранилищу. </param>
        void Add(IDomainObject Domain);

        /// <summary> Удалить объект из хранилища. </summary>
        /// <param name="Domain"> Удаляемый объект. </param>
        void Delete(IDomainObject Domain);

        /// <summary> Присоединить объект к контексту. </summary>
        /// <param name="Object"> Присоединяемый объект. </param>
        /// <param name="BreakExistingRelations"> true, если к контексту следует присоединить только указанный объект без присоединения его навигационных связей, иначе false. </param>
        /// <remarks> Если объект уже присоединен к этому контексту, то метод не производит никакого эффекта. </remarks>
        /// <exception cref="InvalidOperationException"> Объект уже связан с другим контекстом либо имеет состояние, отличное от состояния <c>Domain.EntityState.Detached</c>. </exception>
        void AttachObject(IDomainObject Object, bool BreakExistingRelations = true);

        /// <summary> Отсоединить объект от контекста. </summary>
        /// <param name="Object"> Отсоединяемый объект. </param>
        /// <remarks> Если объект уже отсоединен от контекста, то метод не производит никакого эффекта. </remarks>
        /// <exception cref="InvalidOperationException"> Объект уже связан с другим контекстом. </exception>
        void DetachObject(IDomainObject Object);

        /// <summary> Сохранение всех изменений модели, сделанных в контексте движка, в хранилище данных. </summary>
        int SaveChanges();

        /// <summary> Выполнить команду над хранилищем, возвращающую типизированный набор объектов. </summary>
        /// <param name="FunctionName"> Название функции (команды). </param>
        /// <param name="Parameters"> Параметры команды. </param>
        /// <returns> Массив возвращённых командой объектов - результат выполнения команды. </returns>
        /// <remarks> Выполнение команды по сути есть выполнение хранимой процедуры в хранилище. </remarks>
        T[] ExecuteProcedure<T>(string FunctionName, params Parameter[] Parameters);

        /// <summary> Выполнить команду над хранилищем, возвращающую 2 типизированных набора объектов. </summary>
        /// <typeparam name="T"> Тип первого набора. </typeparam>
        /// <typeparam name="U"> Тип второго набора. </typeparam>
        /// <param name="FunctionName"> Название функции (команды). </param>
        /// <param name="Parameters"> Параметры команды. </param>
        /// <returns> Кортеж массивов возвращённых командой объектов - результат выполнения команды.  </returns>
        /// <remarks> Выполнение команды по сути есть выполнение хранимой процедуры в хранилище. </remarks>
        Tuple<T[], U[]> ExecuteProcedure<T, U>(string FunctionName, params Parameter[] Parameters);

        /// <summary> Выполнить команду над хранилищем, возвращающую 3 типизированных набора объектов. </summary>
        /// <typeparam name="T"> Тип первого набора. </typeparam>
        /// <typeparam name="U"> Тип второго набора. </typeparam>
        /// <typeparam name="K"> Тип третьего набора. </typeparam>
        /// <param name="FunctionName"> Название функции (команды). </param>
        /// <param name="Parameters"> Параметры команды. </param>
        /// <returns> Кортеж массивов возвращённых командой объектов - результат выполнения команды.  </returns>
        /// <remarks> Выполнение команды по сути есть выполнение хранимой процедуры в хранилище. </remarks>
        Tuple<T[], U[], K[]> ExecuteProcedure<T, U, K>(string FunctionName, params Parameter[] Parameters);

        /// <summary> Выполнить команду над хранилищем, возвращающую 4 типизированных набора объектов. </summary>
        /// <typeparam name="T"> Тип первого набора. </typeparam>
        /// <typeparam name="U"> Тип второго набора. </typeparam>
        /// <typeparam name="K"> Тип третьего набора. </typeparam>
        /// <typeparam name="V"> Тип четвертого набора. </typeparam>
        /// <param name="FunctionName"> Название функции (команды). </param>
        /// <param name="Parameters"> Параметры команды. </param>
        /// <returns> Кортеж массивов возвращённых командой объектов - результат выполнения команды.  </returns>
        /// <remarks> Выполнение команды по сути есть выполнение хранимой процедуры в хранилище. </remarks>
        Tuple<T[], U[], K[], V[]> ExecuteProcedure<T, U, K, V>(string FunctionName, params Parameter[] Parameters);

        /// <summary> Выполнить команду над хранилищем, возвращающую произвольное число типизированных наборов объектов. </summary>
        /// <param name="FunctionName"> Название функции (команды). </param>
        /// <param name="Types"> Массив типов, наборы которых должны быть возвращены. </param>
        /// <param name="Parameters">  Параметры команды. </param>
        /// <returns> </returns>
        object[] ExecuteProcedure(string FunctionName, Type[] Types, params Parameter[] Parameters);

        /// <summary> Обновить объект, полученный ранее из источника данных. </summary>
        /// <param name="Entity"> Обновляемый объект. </param>
        /// <param name="KeepEntityChanges"> True, если требуется не перезаписывать изменённые свойства объекта. </param>
        void Refresh(object Entity, bool KeepEntityChanges = false);

        #endregion
    }

    /// <summary> Опции запроса к кешу доменных объектов контекста. </summary>
    public enum QueryCacheOption
    {
        /// <summary> Опции отсутствуют, поиск производится в кеше контекста без обращений к хранилищу. </summary>
        None,
        /// <summary> Присоединение запрашиваемой сущности к контексту, если её нет в кеше. Обращений к хранилищу нет. </summary>
        Attach,
        /// <summary> Запрос в хранилище за новым экземпляром сущности, если её нет в кеше. </summary>
        Query
    }
}
